package webserver;

import java.text.*;
import java.util.*;

/**
 * Der Händler liest den Inhalt des vom Client gesendeten Requests und führet entsprechende Maßnahmen durch.
 * Diese Klasse stellt die Schnittstelle zwischen dem FileManager und dem Server dar. Die im Request verlangten
 * Dinge werden hier umgesetzt.
 * @author Peter
 */
public class Handler {

    // objects
    FileManager fm;
    // ****
    
    // variables
    private String requestStr;    
    private String httpStatusCode;
    private String httpParameters;
    private String httpVersion;
    private String httpMethod;
    private String httpRequestFile;
    private String serverInfo;
    private String firstLineOfRequest;
    private String filePath;

    private String request_strArray[][];

    private byte[] headerBytes;
    private byte[] responseBytes;
    private byte[] fileBytes;

    private boolean keepAlive;
    private boolean fileUnmodifiedSinceRequest;
    private boolean fileModifiedSinceRequest;
    private boolean headersend;
    private boolean status100Continue;

    private SimpleDateFormat dateFormat = new SimpleDateFormat("EEE, d MMM yyyy HH:mm:ss z");

    /**
     * Konstruktor der Klasse Handler.
     * Variablen werden Initialisiert und Vorbelegt.
     * Logfile wird geschrieben
     * @param requestFromClient
     */
    public Handler(String requestFromClient) {

        requestStr = requestFromClient;

        // set header infos from server
        serverInfo = Server.getInstance().getServerName() + " " + Server.getInstance().getServerVersion();
        
        firstLineOfRequest = getFirstLineOfRequest();

        Logging.getInstance().log("(INFO) Received request: "+firstLineOfRequest);

        // init vars...
        fileUnmodifiedSinceRequest = false;
        fileModifiedSinceRequest = false;
        keepAlive = false;
        headersend = false;
        
    }

    /**
     * Erste Line des Requests wird ermittelt.
     * @return Rückgabe der ersten Linie des Response
     */
    private String getFirstLineOfRequest() {
        return requestStr.substring(0, requestStr.indexOf(System.getProperty("line.separator"))).trim();
    }

   /**
     * Suchen der Nutzinformation aus dem empfangenen Request.
     * @param label Name Des Befehls.
     * @return Position an welcher sich der Befehl befindet
     */
    private int getRequestLabelPosition(String label) {

        int value = -1;

        for(int y=0; y<15; y++) {
            if(request_strArray[0][y].contains(label)) {
                value = y;
                break;
            }
        }

        return value;

    }

   /**
     * Ausschneiden des Befehls aus dem Request.
     * @param pos Position des Befehls
     * @return Rückgabe Name des Befehls
     */
    private String getRequestLabelValue(int pos) {

        return request_strArray[1][pos];

    }

    /**
     * Überprüfung auf Keep-Alive Anfrage des Clients.
     * Falls im Request Keep-Alive gesetzt, setze entsprechende boolsche Variable auf true.
     */
    private void setKeepAliveStatus() {

        int posInArray = getRequestLabelPosition("Connection");

        if(posInArray == -1) {
            if(httpVersion.contains("1.1")) keepAlive = true; // there's no valueOfRequest for keep-alive (on 1.1 it's true by default!)
            else keepAlive = false; // not found and no 1.1 -> close connection
        }
        else {
            String valueInArray = getRequestLabelValue(posInArray);
            if(valueInArray.equalsIgnoreCase("keep-alive")) keepAlive = true;
            else keepAlive = false;
        }

    }

   /**
     * Aufruf verschiedener Methoden um festzustellen, welches Protokoll, welche Methode
     * und Pfadangaben zu erhalten. Anschließend wird der Response generiert.
     */
    private void parseRequest() throws ErrorException {

        // first line
        filePath = extractPath(); // get name of file to response
        setHttpMethod(); // GET, POST, HEAD?        
        setHttpParameters(); // 
        setHttpVersion();
        // ******

        // anything else
        request_strArray = new String[2][15]; // Request can't be bigger than 15 lines!!

        for(int y=0; y<15; y++) {
            for(int x=0; x<2; x++) {
                request_strArray[x][y] = "0";
            }
        }

        fillRequestArray(); // fill array with arguments and values from request (argument: valueOfRequest)

        setKeepAliveStatus(); // persistent connection or close connection after repsonse?!

        setMultihomingFilePath(); // HTTP field "Host" will be handled
        // ******
        
    }

   /**
     * Überprüfung ob Multihoming aktiv oder inaktiv.
     *
     */
    private void setMultihomingFilePath() {

        int posInArray = getRequestLabelPosition("Host");
        if(posInArray == -1) {
            // TODO Bad Request -> Host have to be set in request!!
        }
        else {
            String hostName = getRequestLabelValue(posInArray);

            // on localhost we just get the files of www-root
            if(!hostName.equalsIgnoreCase("localhost") && !hostName.isEmpty()) {
                filePath = hostName + "/" + filePath;
            }
        }

    }

   /**
     * Überprüfung ob das im Request gesendete Datum älter als das Erstellungsdatum der
     * Seite ist. Falls ja setze entsprechende Boolsche Variable auf true um später die
     * angeforderte Seite neu zu laden.
     */
    private void checkifmodifiedsince() {

        int posInArray = getRequestLabelPosition("If-Modified-Since");

        if(posInArray == -1) { // field was not sent with request from client
            fileModifiedSinceRequest = false;
        }
        else {
            String valueOfRequest = getRequestLabelValue(posInArray);

            Date fileLastModified = fm.getLastModified();

            try {
                Date request = dateFormat.parse(valueOfRequest);
                String tmpDate = dateFormat.format(fileLastModified);

                if(request.compareTo(fileLastModified) <= 0) { // 304 Not Modified
                    fileModifiedSinceRequest = true;
                }
                else if(request.compareTo(fileLastModified) > 0) { // new response with file
                    fileModifiedSinceRequest = false;
                }
            }
            catch(ParseException ex) {
                System.out.println("[ERROR]: Parse date in Handler.checkIfMod..()");
            }
            catch(NullPointerException exept) {
                System.out.println("[ERROR]: there's something wrong in Handler.checkIfMod...()");
            }
        }

    }

   /**
     * Ist das Feld im Request gesetzt? Falls ja
     * überprüfe ob Seite nicht modifiziert wurde seit der Zeit im Request String.
     */
    private void checkifunmodifiedsince() {

        int posInArray = getRequestLabelPosition("If-Unmodified-Since");
        
        if(posInArray == -1) { // field was not sent with request from client
            fileUnmodifiedSinceRequest = false;
        }
        else {
            String valueOfRequest = getRequestLabelValue(posInArray);

            Date fileLastModified = fm.getLastModified();

            try {
                Date request = dateFormat.parse(valueOfRequest);

                if(request.compareTo(fileLastModified) >= 0) { // 412 Precondition Failed
                    fileUnmodifiedSinceRequest = true;
                }
                else if(request.compareTo(fileLastModified) < 0) { // new response with file
                    fileUnmodifiedSinceRequest = false;
                }
            }
            catch(ParseException ex) {
                System.out.println("[ERROR]: Parse date in Handler.checkIfUnmod...()");
            }
            catch(NullPointerException exept) {
                System.out.println("[ERROR]: there's something wrong in Handler.checkIfUnmod...()");
            }
        }

    }

   /**
     * Auslesen ob im Request das Feld 100-Continue gesetzt wurde. Falls ja
     * setze entsprechende boolsche Variable um später nur mit Header zu antworten.
     */
    private void check100ContinueStatus() {

        if(Server.getInstance().getContinueSupportStatus()) {

            int labelPos = getRequestLabelPosition("Expect");

            if(labelPos == -1) { // no expect found...
                if(httpVersion.contains("1.1")) {
                    status100Continue = true;
                }
                else {
                    status100Continue = false;
                }
            }
            else {
                String value = getRequestLabelValue(labelPos);
                if(value.equalsIgnoreCase("100-continue")) {
                    status100Continue = true;
                }
            }

        }
        else {
            status100Continue = false;
        }
    }

   /**
     * Zusemmenbau des Headers anhend der Teilinformationen aus den anderen Methoden.
     * @return Response Header
     */
    private String buildHeader() {

        String header = new String();
        
        fileBytes = fm.readFile();
        httpStatusCode = fm.getErrorCode();

        checkifmodifiedsince(); // set bool var to decide which response we have to sent
        checkifunmodifiedsince(); // set bool var to decide which response we have to sent

        check100ContinueStatus();

        // *** CREATE HEADER ***

        // set version
        if(httpVersion.contains("1.0")) {
            header = "HTTP/1.0 ";
        }
        else {
            header = "HTTP/1.1 ";
        }

        // set status code

        if(status100Continue) {
            header += "100 Continue\r\n\r\n";
            return header;
        }

        if(fileModifiedSinceRequest) { // true => 304 Not Modified
            header += Status.getInstance().getStatusCode(304) + "\r\n";
        }
        else if(fileUnmodifiedSinceRequest) { // true => 412 Precondition Failed
            header += Status.getInstance().getStatusCode(412) + "\r\n";
        }
        else {
            header += fm.getErrorCode() + "\r\n";
        }

        // set current date of response
        header += "Date: " + getCurrentTime() + "\r\n";

        // set server info
        header += "Server: " + serverInfo + "\r\n";

        // set last modified (only if requested file could be read!)
        if(httpStatusCode.contains("200") && (!fileModifiedSinceRequest || !fileUnmodifiedSinceRequest)) {
            header += "Last-Modified: " + dateFormat.format(fm.getLastModified()) + "\r\n";
        }

        
        if(!fileModifiedSinceRequest && !fileUnmodifiedSinceRequest) {
            // set content-type
            header += "Content-Type: " + fm.getMIMEType() + "\r\n";

            // set content-length
            header += "Content-Length: " + fm.getContentLength() + "\r\n";
        }

        // persistent connection?
        if(keepAlive) {
            header += "Connection: keep-alive\r\n";
        }
        else {
            header += "Connection: close\r\n";
        }

        // set blank line to end the header infos
        header += "\r\n";
        
        // *** CREATE HEADER ***

        return header;

    }

   /**
     * Zusammenbau der Antwort. Entsprechend der verwendeten Methode wird hier
     * die Antwort generiert. Diese beinhaltet Header und Rumpf (GET).
     * @return Response in Form einer byte Arrays
     */
    private byte[] buildResponse() {

        String header = buildHeader(); // create header
        headerBytes = header.getBytes(); // put header into byte-array

        // create log
        String log_response = header.substring(0, header.indexOf("Date: ")-1);
        Logging.getInstance().log("(INFO) Response: "+log_response);
        // ******

        if(httpMethod.equals("HEAD")) { // only header will be sent!
            
            responseBytes = new byte[headerBytes.length];

            // copy header into response
            System.arraycopy(headerBytes, 0, responseBytes, 0, headerBytes.length); //src, srcPos, dest, destPos, length

            return responseBytes;
        }
        
        if(httpMethod.equals("GET") || httpMethod.equals("POST")) { // send header + file
            
            
            // ONLY HEADER
            if(fileModifiedSinceRequest || fileUnmodifiedSinceRequest || status100Continue) {
                responseBytes = new byte[headerBytes.length];

                // create response
                System.arraycopy(headerBytes, 0, responseBytes, 0, headerBytes.length); // header to array
            }
            else { // HEADER + FILE
                responseBytes = new byte[headerBytes.length+fileBytes.length];

                // create response
                System.arraycopy(headerBytes, 0, responseBytes, 0, headerBytes.length); // header to array
                System.arraycopy(fileBytes, 0, responseBytes, headerBytes.length, fileBytes.length); // file to array
            }
            
            return responseBytes;
        }

        
        // code below sould never be executed!!

        // Code: 500
        return "FEHLER".getBytes();
        
    }

   /**
     * Auslesen der Informationen die vom Client gesendet wurden. Diese werden in ein Array geschrieben
     * zum besseren Händling.
     */
    private void fillRequestArray() {

        String tmpRequest = requestStr; // "backup" of request (will be cut)

        // delete first line
        tmpRequest = tmpRequest.substring(tmpRequest.indexOf(System.getProperty("line.separator"))+1).trim();

        // split doublepoint and line -> save to array
        for(int y=0; y<15; y++) {

            String row = tmpRequest.substring(0, tmpRequest.indexOf(System.getProperty("line.separator"))+1).trim();

            if(row.equals("")) {
                break;
            }

            String row_values[] = row.split(":");
            String argument = row_values[0].trim();
            String value = "";


            if(row_values.length > 2) {
                for(int i=1; i<row_values.length; i++) {
                    if(i == row_values.length-1) { // reached end of line
                        value += row_values[i].trim();
                    }
                    else {
                        value += row_values[i].trim() + ":";
                    }
                }
            }
            else {
                value = row_values[1].trim();
            }

            request_strArray[0][y] = argument;
            request_strArray[1][y] = value;

            tmpRequest = tmpRequest.substring(tmpRequest.indexOf(System.getProperty("line.separator"))+1).trim();

        }

    }

   /**
     * Auslesen ser aktuellen Datum und Uhrzeit. Diese wird nachher verglichen mit dem Erstellungsdatum
     * der Seite um das Feld Mofified zu bedienen.
     * @return
     */
    private String getCurrentTime() {
        Date currentDate = new Date();
        return dateFormat.format(currentDate);
    }

   /**
     * Überprüfung ob Keep Alive aktiv. Dadurch weiß der Server Ob die
     * Verbindung nach behandeltem Request die Verbindung wieder geschlossen werden kann.
     * @return Keep-Alive Ein-/Aus
     */
    public boolean getKeepAlive() {

        if(keepAlive) return true;
        else return false;

    }

   /**
     * Aufruf der Methode ParseRequest um Informationen aus Request zu Erhalten.
     * Ein neues Objekt der Klasse FileManager erzeugen.
     * @return Empfangener Request des Clients
     */
    public byte[] getResponse() {

        boolean error = false;

        try {
            parseRequest();
        }
        catch(ErrorException myEx) {
            String errorText = myEx.getMessage();

            if(errorText.equals("bad request")) {
                // Status: 400
                error = true;

                httpStatusCode = Status.getInstance().getStatusCode(400);

                
            }
            else if(errorText.equals("wrong http method")) {
                // Status: 501
                error = true;

                httpStatusCode = Status.getInstance().getStatusCode(501);
            }

        }

        if(error) {

            String header = new String();

            // set version
            if(httpVersion.contains("1.0")) {
                header = "HTTP/1.0";
            }
            else {
                header = "HTTP/1.1";
            }

            header += httpStatusCode;

            header += "Date: " + getCurrentTime() + "\r\n";

            header += "Server: " + serverInfo + "\r\n";

            header += "Content-Type: text/html \r\n";

            if(keepAlive) {
                header += "Connection: keep-alive\r\n";
            }
            else {
                header += "Connection: close\r\n";
            }

            // create blank line to end header area
            header += "\r\n";

            return header.getBytes();
        }
        else {
            fm = new FileManager(filePath);
            return buildResponse();
        }
    }

    /**
     * holt aus der request die HTTP Version und setzt die entsprechende Variable
     */
    private void setHttpVersion() throws ErrorException {

        if(firstLineOfRequest.contains(("HTTP/1.0"))) {
            httpVersion = "HTTP/1.0";
        }
        else if(firstLineOfRequest.contains("HTTP/1.1")) {
            httpVersion = "HTTP/1.1";
        }
        else if(firstLineOfRequest.contains("HTTP/0.9")) {
            // 501 Not Implemented
        }
        else {
            // no correct version found!
            // => 400 bad request
            throw new ErrorException("bad request");

        }

    }

   /**
     * Überprüfung der zu verwendenden Methode. Und auslesen der vom Client gesetzten Daten.
     *
     */
    private void setHttpParameters() {

        String param = "";
        String pathArray[];

        if(httpMethod.equals("GET")) {
            pathArray = firstLineOfRequest.substring(4).split(" ");

            if(firstLineOfRequest.contains("?")) {
                param = pathArray[0].substring(pathArray[0].indexOf("?")+1).trim();
                httpParameters = param;

                Logging.getInstance().log("(INFO) GET-Parameters: "+httpParameters);
            }
            else {
                httpParameters = ""; // no parameters to extract!
            }

            return;
        }

        if(httpMethod.equals("POST")) {

            httpMethod = "POST";

                String breakLine = System.getProperty("line.separator")+System.getProperty("line.separator");
                param = requestStr.substring(requestStr.indexOf(breakLine)).trim();

                httpParameters = param;

                Logging.getInstance().log("(INFO) POST-Parameters: "+httpParameters);

                return;
        }

        if(httpMethod.equals("HEAD")) {

            httpParameters = "";

            return;
        }

        // code below should never be executed!!

        // --> // httpStatus = "501 Not Implemented";

        Logging.getInstance().log("(ERROR) Could not find a valid HTTP-Method in \""+ firstLineOfRequest +"\"");

    }

   /**
     * Durchsuchen nach vom Client geforderter Methode und setzen der Glob. Variable httpMethod.
     */
    private void setHttpMethod() throws ErrorException {

        if(firstLineOfRequest.startsWith("GET") || firstLineOfRequest.startsWith("get")) {
            httpMethod = "GET";
            return;
        }
        if(firstLineOfRequest.startsWith("POST") || firstLineOfRequest.startsWith("post")) {
            httpMethod = "POST";
            return;
        }
        if(firstLineOfRequest.startsWith("HEAD") || firstLineOfRequest.startsWith("head")) {
            httpMethod = "HEAD";
            return;
        }
        else {
            // 501 Not Implemented
            throw new ErrorException("wrong http method");
        }

    }
    
   /**
     * Auslesen der Pfadangebe aus dem Request.
     * @return Pfad wo sich die Angeforderte Seite befintet.
     */
    private String extractPath() {

        String path = "";
        String pathArray[];

        pathArray = firstLineOfRequest.split(" ");

        if(pathArray[1].equals("/")) {
            return "/index.html";
        }

        if(firstLineOfRequest.contains("?")) {            
            path = pathArray[1].substring(1, pathArray[1].indexOf("?")).trim();
        }
        else {
            path = pathArray[1].substring(1).trim();
        }

        return path;

    }



}
